package com.huahai.security.core.validate.code.impl;

import com.huahai.security.core.exception.ValidateCodeException;
import com.huahai.security.core.properties.StatusCodeEnum;
import com.huahai.security.core.supports.ResultResponse;
import com.huahai.security.core.util.UUID;
import com.huahai.security.core.validate.code.ValidateCode;
import com.huahai.security.core.validate.code.ValidateCodeGenerator;
import com.huahai.security.core.validate.code.ValidateCodeProcessor;
import com.huahai.security.core.validate.code.ValidateCodeType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.ServletRequestUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author: Jun
 * @Date: 2020/7/17
 *
 * 抽象的验证码处理器
 */
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现。
     */
    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGenerators;

    @Override
    public ResultResponse create(HttpServletRequest request, HttpServletResponse response) throws Exception {
        C validateCode = generate(request);
        save(request, validateCode);
        return send(request,response, validateCode);
    }

    @Override
    public void validate(HttpServletRequest request) {
        ValidateCodeType processorType = getValidateCodeType(request);
        String uuid = ServletRequestUtils.getStringParameter(request,"uuid","");
        String redisKey = getRedisKey(uuid);

        String codeInRedis = stringRedisTemplate.opsForValue().get(redisKey);

        String codeInRequest;
        try {
            codeInRequest = ServletRequestUtils.getStringParameter(request,
                    processorType.getParamNameOnValidate());
        } catch (Exception e) {
            throw new ValidateCodeException(StatusCodeEnum.HSC400002.getCode(),StatusCodeEnum.HSC400002.getMessage());
        }

        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException(StatusCodeEnum.HSC400003.getCode(),StatusCodeEnum.HSC400003.getMessage());
        }

        if (codeInRedis == null) {
            stringRedisTemplate.delete(redisKey);
            throw new ValidateCodeException(StatusCodeEnum.HSC400004.getCode(),StatusCodeEnum.HSC400004.getMessage());
        }

        if (!StringUtils.equals(codeInRedis ,codeInRequest)) {
            throw new ValidateCodeException(StatusCodeEnum.HSC400005.getCode(),StatusCodeEnum.HSC400005.getMessage());
        }

        stringRedisTemplate.delete(redisKey);
    }

    /**
     * 发送校验码  由子类实现
     * @param request
     * @param response
     * @param validateCode
     * @throws Exception
     * @return
     */
    protected abstract ResultResponse send(HttpServletRequest request, HttpServletResponse response, C validateCode) throws Exception;

    /**
     * 生成校验码
     *
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")
    private C generate(HttpServletRequest request) {
        String type = getValidateCodeType(request).toString().toLowerCase();
        String generatorName = type + ValidateCodeGenerator.class.getSimpleName();
        ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName);
        if (validateCodeGenerator == null) {
            throw new ValidateCodeException(StatusCodeEnum.HSC400006.getCode(),StatusCodeEnum.HSC400006.getMessage());
        }
        return (C) validateCodeGenerator.generate(request);
    }

    /**
     * 保存生成的校验码
     * @param request
     * @param validateCode
     */
    private void save(HttpServletRequest request, C validateCode) {
        String uuid = UUID.fastUUID().toString();
        validateCode.setUuid(uuid);
        stringRedisTemplate.opsForValue().set(getRedisKey(uuid),validateCode.getCode(),validateCode.getExpireIn(), TimeUnit.MILLISECONDS);
    }

    /**
     * 构建验证码放入REDIS时的key
     *
     * @param request
     * @return
     *
     * 这里的产生验证码的逻辑如果在参数中得不到值的话，多人同时访问的时候可能会出错
     */
    private String getRedisKey(String uuid) {
        String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");
        return REDIS_KEY_PREFIX + type.toUpperCase() + "_" + uuid;
    }

    /**
     * 根据请求的url获取校验码的类型
     *
     * @param request
     * @return
     */
    private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
        String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");

        return ValidateCodeType.valueOf(type.toUpperCase());
    }
}
